#include <sstream>
#include "parser.h"
#include "logger.h"
#include "coursefactory.h"
#include "registrar.h"
#include "context.h"
#include <boost/lexical_cast.hpp>
#include "credits.h"
#include "or.h"
#include "and.h"
#include "not.h"
#include "norequirement.h"
#include <boost/date_time/gregorian/gregorian.hpp>

using namespace std;

using boost::posix_time::ptime;
using boost::posix_time::time_period;
typedef xmlpp::Node::NodeList NodeList;
typedef NodeList::iterator NodeListIterator;

/// The one-and-only Parser instance.
Parser* Parser::instance_;

Parser::Parser() :
	_prefix(""),
	_studentFilePath(""),
	_courseFilePath(""),
	_curriculumFilePath("")
{
    init();
}

Parser::~Parser()
{
    delete instance_;
}

/// Gain access to the one-and-only Parser instance.
/// \return The one-and-only Parser instance.
Parser* Parser::instance()
{
    if( instance_ == 0 )
        instance_ = new Parser();
    return instance_;
}

bool Parser::init()
{
    parser.set_substitute_entities(); //&entities; -> "value"
	return true;
}

void Parser::prefix(path prefix)
{
	_prefix = prefix;
	if(_prefix.size() > 0)
		{
			size_t pos = _prefix.find_last_of('/');
			
			if( pos != std::string::npos || pos != _prefix.size()-1 )
				_prefix.push_back('/');
						
		}
}
std::string Parser::prefix() const
{return _prefix;}
std::string fixfilepath(const std::string& thepath)
{
	std::string filepath = thepath;
	while(filepath.size()>0 && filepath[0] == '/')
		{
			filepath = filepath.substr(1);
		}
	return filepath;
}
void Parser::student(path student)
{_studentFilePath = fixfilepath(student);}
std::string Parser::student() const
{return _studentFilePath;}
void Parser::courses(path courses)
{_courseFilePath = fixfilepath(courses);}
std::string Parser::courses() const
{return _courseFilePath;}

bool Parser::parse()
{
    try
		{
			doCourses();

			doStudent();

			doCurriculum();
	    
		}
    catch(const std::exception& ex)
		{
			cerr << "Exception caught: " << ex.what() << endl;
			return false;
		}

    return true;
}


bool Parser::doStudent()
{

	stringstream ss;
    ss << "Reading student from: " << _prefix + _studentFilePath;
	Logger::instance()->message(ss.str(), 3);

    parser.parse_file(_prefix + _studentFilePath);
    
    
    if(!parser)
		return false;
    
    
    document = parser.get_document();
    xmlpp::Element* pNode = document->get_root_node();
    
    
    // pNode = root of document.
    // Load the student with his info.
	Student _student;
	_student.setFirstName( pNode->get_attribute("first")->get_value() );
    _student.setLastName( pNode->get_attribute("last")->get_value() );
    _student.setStudentID( pNode->get_attribute("id")->get_value() );

	
    _student.setPassword( pNode->get_attribute("password")->get_value() );
    _student.setCurriculumFile( pNode->get_attribute("curriculum")->get_value() );


	// Create the courses that have been taken.
	NodeList completed = pNode->get_children("completed");
	completed = (dynamic_cast<xmlpp::Element*>(*completed.begin()))->get_children("course");

	for( NodeListIterator i = completed.begin();
		i != completed.end();
		++i )
		{
			std::string name = (dynamic_cast<xmlpp::Element*>(*i))->get_attribute("name")->get_value();
			std::string id = (dynamic_cast<xmlpp::Element*>(*i))->get_attribute("id")->get_value();
			_student.addCompletedCourse(CourseFactory::instance()->get(name+id));
		}

	
	// TODO: parseConstraints.
	
	// parseConstraints();
	
	// TODO: add constraints to the student.
	
	
	
	Registrar::instance()->student(_student);

    std::stringstream out;
    out << _student << " successfully loaded from: " << _studentFilePath <<".";
    Logger::instance()->message( out.str(), 5);



    return true;
}

bool Parser::doCourses()
{
	stringstream ss;
    ss << "Reading courses from: " << _prefix + _courseFilePath;
	Logger::instance()->message(ss.str(), 3);

	parser.parse_file(_prefix + _courseFilePath);
    if(!parser)
		return false;

    document = parser.get_document();
    xmlpp::Element* pRoot = document->get_root_node();

    std::string courseRootName = "classes";
    if ( pRoot->get_name() != courseRootName ) //The xml file is incorrect.
		{
			Logger::instance()->error("Invalid xml, " + _courseFilePath, 1);
			return false;
		}
    // OK our courses.xml file seems ok so far.
	typedef xmlpp::Node::NodeList XNodeList; 
    XNodeList classes = pRoot->get_children("class");
    for(XNodeList::iterator it = classes.begin() ; 
		it != classes.end() ; it++ )
		{
			// (*it) is a Node pointer.
			//it is a node, i.e. a set of info for class.
			std::string name, id, description, credits;
			xmlpp::Element* element = dynamic_cast<xmlpp::Element *>(*it);

			description = element->get_attribute("description")->get_value();
			name = element->get_attribute("name")->get_value();
			id = element->get_attribute("number")->get_value();
			credits = element->get_attribute("credits")->get_value();
			
			
			boost::shared_ptr<Course> pCourse ( CourseFactory::instance()->get(name+id) );
			pCourse->setDescription(description);
			pCourse->setCredits(boost::lexical_cast<double>(credits));

			
			std::stringstream out;
			out << *pCourse << ", loaded.";
	    
			
			XNodeList sections = element->get_children("sections");
			XNodeList::iterator sit = sections.begin();
			
			sections = (*sit)->get_children();
			

			boost::shared_ptr<CourseSection> section;
			for ( sit = sections.begin() ; sit != sections.end() ; ++sit)
				{
					std::string node_name = (*sit)->get_name();
					if(node_name=="text")continue;
					
					doSection(dynamic_cast<xmlpp::Element *>(*sit), pCourse, section);
				}
			
			XNodeList req = element->get_children("requirements");

			if( req.size() > 0 )
				{	
					boost::shared_ptr<Requirement> req_tree(
															doRequirements(dynamic_cast<xmlpp::Element*>(req.front()))
															);
					pCourse->prerequisites(req_tree);
					
					if ( req.size() != 1 )
						{
							// TODO: MAKE THIS A WARNING
							Logger::instance()->message( "Extra requirements tags ignored, you might want to check: " + _courseFilePath +". It may contain errors."  , 9);
						}

				}
			else
				{
					pCourse->prerequisites(boost::shared_ptr<Requirement>(new NoRequirement()) );
					Logger::instance()->message( "No requirements found, creating NoRequirement.", 8);
				}
			

			Logger::instance()->message( out.str(), 7);
		}
			
	//Courses are input into the vector of courses

	return true;
}

void Parser::doSection(xmlpp::Element * e, boost::shared_ptr<Course> course, boost::shared_ptr<CourseSection> parent)
{
	using boost::lexical_cast;
	using namespace boost;
	using namespace gregorian;
	using namespace posix_time;
	typedef nth_day_of_the_week_in_month nth_dow;
	greg_weekday weekdays[] = {Monday,Tuesday,Wednesday,Thursday,Friday};

	if(!e)
		return;

	std::string node_name = e->get_name();

	if( !(node_name == "lecture" || node_name == "tutorial" || node_name == "lab" ) )
		return;

	
	CourseSection::SectionType type;
	if(node_name=="lecture")
		type = CourseSection::L;
	else if( node_name =="tutorial")
		type = CourseSection::T;
	else
		type = CourseSection::X;


	xmlpp::Attribute* attribute;
	std::string id, prof, location;
	int semester =0;

	attribute = e->get_attribute("name");
	if(attribute)
		id = attribute->get_value();

	attribute = e->get_attribute("professor");
	if(attribute)
		prof = attribute->get_value();

	attribute = e->get_attribute("location");
	if(attribute)
		location = attribute->get_value();

	attribute = e->get_attribute("semester");
	if(attribute)
		semester = lexical_cast<int>(attribute->get_value());
	

	
	boost::shared_ptr<CourseSection> section(new CourseSection
											 (id,
											  prof,
											  location,
											  semester,
											  type,
											  course,
											  parent
											  ));


	//Add this section to the course, if it is a top level course.
	if ( !parent.get() ){ // If parent points to nothing: we have no parent, we need to 
		// add this to the the course.
		course->addSection(section);
	}
	

	std::stringstream sectionOut;
	sectionOut <<"Section " << id <<", prof("<< prof <<"), loaded." ;
	Logger::instance()->message(sectionOut.str(), 8);


	NodeList sub_sections = e->get_children();
	
	for(NodeListIterator i = sub_sections.begin(); i != sub_sections.end()  ;++i)
		{
			node_name = (*i)->get_name();
			if(node_name=="text")continue;
			Logger::instance()->message(node_name,9);
			if(node_name == "lecture" || node_name == "tutorial" || node_name == "lab" )
				doSection(dynamic_cast<xmlpp::Element *>(*i), course, section);
			else if (node_name == "time")
				{
					xmlpp::Element* time_node = dynamic_cast<xmlpp::Element *>(*i);
					std::string day, begin, end;
					attribute = time_node->get_attribute("day");
					if(attribute)
						day = attribute->get_value();
					attribute = time_node->get_attribute("begin");
					if(attribute)
						begin = attribute->get_value();
					attribute = time_node->get_attribute("end");
					if(attribute)
						end = attribute->get_value();
					
					time_duration start, finish;
					try
						{
							start = str_to_time(begin);
							finish = str_to_time(end);
						}
					catch(std::exception e)
						{
							stringstream ss;
							ss << "begin=\"" << begin << "\" or end=\"" <<
								end << "\" malformed on line: ";
							ss << time_node->get_line();
							Logger::instance()->warning(ss.str() ,0);
							continue;
						}
							

					// day might = "MW" begin = "0845" end ="1000"
					

					// This is ugly, i know.
					bool days[] = {false,false,false,false,false};
					
					for(std::size_t j = 0 ; j < day.size() ; ++j)
						switch(day[j])
							{
							case 'M' : case 'm':
								days[0] = true;
								break;
							case 'T' : case 't':
								days[1] = true;
								break;
							case 'W' : case 'w':
								days[2] = true;
								break;
							case 'R' : case 'r': case 'J': case 'j':
								days[3] = true;
								break;
							case 'F' : case 'f':
								days[4] = true;
								break;
							}

					for(std::size_t j = 0 ; j < sizeof(days)/sizeof(bool) ; ++j)
						{
							// Does it occur on this day of week?
							if(!days[j])
								continue;

							//Magic happens here and our times contain correct values;
							nth_dow dow (nth_dow::second, weekdays[j],Jan);
							date day = dow.get_date(2002);
							

							section->time(ptime(day,start),ptime(day,finish));
						}
				}
		}
	
}

boost::posix_time::time_duration Parser::str_to_time(const string& time)
{
	using boost::lexical_cast;
	int h, m;
	h = lexical_cast<int>(time.substr(0,2));	
	m = lexical_cast<int>(time.substr(2,2));

	return boost::posix_time::hours(h) + boost::posix_time::minutes(m);

}

boost::shared_ptr<Requirement> Parser::doRequirements(xmlpp::Element* node)
{
	std::string node_name = node->get_name();
	boost::shared_ptr<Requirement> lreq, rreq;
	boost::shared_ptr<Requirement> req;

	Logger::instance()->message("Requirements processing on node "+node_name+" starting." ,9);
	

	// We've been called by the outside function and pointing at the
	// node. Find the requirement and create it, or make NoReq.
	if(node_name== "requirements" )
		{
			NodeList req = node->get_children();
			vector<string> ignored;
			xmlpp::Node* pNode = 0;

			for(NodeList::iterator i = req.begin() ; i!=req.end() ;++i)
				{
					string name = (*i)->get_name();
					
					if(name == "text" || name=="comment")
						continue;

					if(!pNode)
						pNode = *i;
					else
						ignored.push_back(name);
				}
			if(pNode)
				{
					for(std::size_t i = 0 ; i < ignored.size() ; ++i)
						Logger::instance()->message
							( "Ignoring node: " 
							  + ignored[i] 
							  + " inside requirements. You might have to fix your xml."
							  ,8);
					// TODO: Above into warning.
					
					return doRequirements(dynamic_cast<xmlpp::Element*> (pNode) );
				}
			else
				{
					return boost::shared_ptr<Requirement>(new NoRequirement);
				}
		}


	if( node_name == "and" ||
		node_name == "or" ||
		node_name == "not")
		{
			
			NodeList children = node->get_children();
			NodeListIterator ichild = children.begin();

			// TODO: Write a warning! here
			
			while ((*ichild)->get_name()=="text" ||
				   (*ichild)->get_name()=="comment")
				++ichild;
			
			xmlpp::Node* child = *ichild;
			lreq = doRequirements(dynamic_cast<xmlpp::Element*>(child));
			
			if(node_name == "and" || node_name == "or")
				{
					do
						{++ichild;}
					while ((*ichild)->get_name()=="text" ||
				   (*ichild)->get_name()=="comment");

					xmlpp::Node* child = *ichild;
					rreq = doRequirements(dynamic_cast<xmlpp::Element*>(child));
				}

			if(node_name == "and")
				req = shared_ptr<Requirement>(new And (lreq, rreq));
			if(node_name == "or")
				req = shared_ptr<Requirement>(new Or  (lreq, rreq));
			if(node_name == "not")
				req = shared_ptr<Requirement>(new Not (lreq));
		   
		}
	else if (node_name == "course")
		{
			std::string name, id;
			xmlpp::Attribute* attribute;
			attribute = node->get_attribute("name");
			if(attribute)
				name = attribute->get_value();
			attribute = node->get_attribute("number");
			if(attribute)
				id = attribute->get_value();
			
			req = CourseFactory::instance()->get(name+id);
		}
	else if(node_name == "credits")
		{
			using boost::lexical_cast;
			std::string value;
			xmlpp::Attribute* attribute;
			attribute = node->get_attribute("ge");
			if(attribute)
				value = attribute->get_value();

			double cred = lexical_cast<double>(value);
			req = shared_ptr<Requirement>(new Credits (cred));
		}
    else if(node_name == "norequirement")
		{
			req = shared_ptr<Requirement>(new NoRequirement);
		}
	else
		{
			// CHANGE THIS BACK TO A WARNING!!!!!
			if(!(node_name == "text" || node_name == "comment" ))
				Logger::instance()->message( "Junk in requirements, node " + node_name + " ignored."  ,5 );
			req = shared_ptr<Requirement>(new NoRequirement);
		}
	
	
	return req;
}
bool Parser::doCurriculum()
{

	Logger* logger = Logger::instance();
	// Needs more info for now.
	Curriculum _curriculum;
	
	_curriculumFilePath = Registrar::instance()->student().getCurriculumFile();

	stringstream ss;
    ss << "Reading curriculum from: " << _prefix + _curriculumFilePath;
	logger->message(ss.str(), 3);


	parser.parse_file(_prefix + _curriculumFilePath );
	document = parser.get_document();
	xmlpp::Element* pRoot = document->get_root_node();
	// WE HAVE THE ROOT OF THE CURRICULUM TREE.
	if( pRoot->get_name() != "curriculum")
		{
			std::stringstream out;
			out << _curriculumFilePath << " is invalid." ;
			logger->error( out.str(), 1);
			exit(-1);
		}

	_curriculum.description( pRoot->get_attribute("name")->get_name() );

	xmlpp::Node::NodeList list = pRoot->get_children("group");
	
	for(xmlpp::Node::NodeList::iterator i = list.begin() ;i != list.end() ; ++i)
		{
			xmlpp::Element* e = dynamic_cast<xmlpp::Element *>(*i);
			std::string gtype = e->get_attribute("type")->get_value();
			
			doGroup(e, gtype, _curriculum);//diegolds.

			if(gtype=="elective")
				{
					std::string creds = e->get_attribute("credits")->get_value();
					_curriculum.elective_credits(boost::lexical_cast<double>(creds) );
				}		
					
		}

   	Registrar::instance()->curriculum(_curriculum);
	return true;
}

void Parser::doGroup(xmlpp::Element* element, std::string type, Curriculum& _curr)
{
	if( !(type=="elective" || type=="core")  ) //we only understand these.
		return;
	
	typedef void (Curriculum::*push_function)(boost::shared_ptr<Course>);

	//ugly but this reduces if()'s and code duplication;
	push_function push_back = 
		(type=="core")?&Curriculum::push_back_core:&Curriculum::push_back_elective;
	
	NodeList classes_list = element->get_children("class");
    for(NodeListIterator i = classes_list.begin();
		i != classes_list.end();
		++i )
		{
			std::string name, number;
			xmlpp::Element* node = dynamic_cast<xmlpp::Element *>(*i);
			
			xmlpp::Attribute* attribute;
			attribute = node->get_attribute("name");
			name = (attribute)?attribute->get_value():"";
            
			attribute = node->get_attribute("number");
			number = (attribute)?attribute->get_value():"";

			(_curr.*push_back)(
			CourseFactory::instance()->get(name+number));
			
		}
	
}

// Requirements parsing.
// TODO:  add to Parser class and integrate with the class
void doRequirements()
{
    using boost::lexical_cast;
    using boost::shared_ptr;
    
    std::string credits_req_token; // e.g. 60credits
    std::string course_req_token; // e.g. comp248
    std::string another_course_req_token; // e.g. soen490

    // ... parse requirements ...

    // Create a Credits requirement
    shared_ptr<Credits> credits_req(
									new Credits(lexical_cast<double>(credits_req_token)));

    // Create a Course requirement
    CourseFactory *course_factory = CourseFactory::instance();
    shared_ptr<Course> course_req(course_factory->get(course_req_token));

    // OR the credits and course requirements together
    shared_ptr<Or> or_req(
						  new Or(credits_req, course_req)); // 60credits OR comp248

    // Lets do another course requirement
    shared_ptr<Course> another_course_req(
										  course_factory->get(another_course_req_token));

    // Negate it
    shared_ptr<Not> negated_course_req(new Not(another_course_req));

    // ... and OR that negation with the previous OR'd requirements
    shared_ptr<Or> the_requirements(
									new Or(
										   or_req, negated_course_req)); // (60credits OR comp248) OR NOT comp490

    // later in the code, when evaluating requirements to see if a schedule
    // fulfills the requirements, we'll do
    // 
    // Context context;
    // context.set_student_information(to_that);
    // Requirement req = get_the_requirements(); // returns the previously
    //                                           // instantiated object
    //                                           // the_requirements
    // bool requirements_fulfilled = requirements->evaluate(context);
    //
    // See the code for Credits::evaluate(), Or::evaluate() etc. to understand
    // how requirements evaluation works.  It is very straighforward;
    // Credits::evaluate() is 7 lines of code, Or::evaluate() is 1 line of
    // code.
}
